<html>
    <head>
        <title>规约</title>
    </head>
    <body>
        <script>
            // 规约 Specification

                    /*
                            规约用于为实体和其他业务对象声明 名称、是否可重用、可组合和可测试的类。

                            规约，也是领域层的一部分。
                    */
                   /*
                        一、安装

                                当你使用启动模板时，此软件包已安装。所以，大多数时候你不需要手动安装它。

                                将" Volo.Abp.Specification" NuGet 包安装到你的项目中。

                                当前文件夹是项目的根文件夹（.csproj）时，你可以在命令行终端中使用ABP CLIadd-pack命令：

                                    "abp add-package Volo.Abp.Specifications"
                   */

                   /*
                       二、定义规约

                               假设你有一个定义如下的客户实体：

                                        using System;
                                        using Volo.Abp.Domain.Entities;

                                        namespace MyProject
                                        {
                                            public class Customer : AggregateRoot<Guid>
                                            {
                                                public string Name { get; set; }

                                                public byte Age { get; set; }

                                                public long Balance { get; set; }

                                                public string Location { get; set; }
                                            }
                                        }

                               你可以创建从Specification<Customer>派生的新规约类：

                                    eg:  选择18岁以上客户的规范：

                                        using System;
                                        using System.Linq.Expressions;
                                        using Volo.Abp.Specifications;

                                        namespace MyProject
                                        {
                                            public class Age18PlusCustomerSpecification : Specification<Customer>
                                            {

                                                // 你只需要定义一个Lambda表达式，来定义规约
                                                public override Expression<Func<Customer, bool>> ToExpression()
                                                {
                                                    return c => c.Age >= 18;
                                                }
                                            }
                                        }

                                相反，你可以直接实现ISpecification<T>接口，但Specification<T>基类大大简化了它。
                   */

                   /*
                        三、使用规约

                                    规约有两个常见的用例：

                                    1、IsSatisfiedBy 方法

                                            IsSatisfiedBy方法可用于检查"单个对象"是否满足规范。

                                            eg:  如果客户不满足年龄规范，则抛出异常

                                                using System;
                                                using System.Threading.Tasks;
                                                using Volo.Abp.DependencyInjection;

                                                namespace MyProject
                                                {
                                                    public class CustomerService : ITransientDependency
                                                    {
                                                        public async Task BookRoom(Customer customer)
                                                        {
                                                            if (!new Age18PlusCustomerSpecification().IsSatisfiedBy(customer))   // 新建规约实例，并检查是否满足规约
                                                            {
                                                                throw new Exception(
                                                                    "This customer doesn't satisfy the Age specification!"
                                                                );
                                                            }
                                                            
                                                            //TODO...
                                                        }
                                                    }
                                                } 


                                    2、使用ToExpression 方法将规约转化为Lambda表达式，然后再存储库中使用它。

                                                ToExpression()方法可用于将规范用作表达式。这样，你可以在从数据库查询时使用规范来过滤实体。

                                                            using System;
                                                            using System.Collections.Generic;
                                                            using System.Linq;
                                                            using System.Threading.Tasks;
                                                            using Volo.Abp.DependencyInjection;
                                                            using Volo.Abp.Domain.Repositories;
                                                            using Volo.Abp.Domain.Services;

                                                            namespace MyProject
                                                            {
                                                                public class CustomerManager : DomainService, ITransientDependency
                                                                {
                                                                    private readonly IRepository<Customer, Guid> _customerRepository;

                                                                    public CustomerManager(IRepository<Customer, Guid> customerRepository)
                                                                    {
                                                                        _customerRepository = customerRepository;
                                                                    }

                                                                    public async Task<List<Customer>> GetCustomersCanBookRoom()
                                                                    {
                                                                        var queryable = await _customerRepository.GetQueryableAsync();
                                                                        var query = queryable.Where(
                                                                            new Age18PlusCustomerSpecification().ToExpression()  // 新建规约实例，并转化为Lambda表达式

                                                                        );
                                                                        
                                                                        return await AsyncExecuter.ToListAsync(query);// 在存储库中使用表达式过滤
                                                                    }
                                                                }
                                                            }

                                                实际上，不需要使用ToExpression()方法，因为规范会自动转换为表达式。这也可以：

                                                            var queryable = await _customerRepository.GetQueryableAsync();
                                                            var query = queryable.Where(
                                                                new Age18PlusCustomerSpecification()
                                                            );

                   */

                   /*
                        四、组合规约

                                规约的一个强大特性是它们可与And、Or、Not和AndNot扩展方法组合。

                                假设你有另一个定义如下的规范：

                                        using System;
                                        using System.Linq.Expressions;
                                        using Volo.Abp.Specifications;

                                        namespace MyProject
                                        {
                                            public class PremiumCustomerSpecification : Specification<Customer>
                                            {
                                                public override Expression<Func<Customer, bool>> ToExpression()
                                                {
                                                    return (customer) => (customer.Balance >= 100000);
                                                }
                                            }
                                        }


                                你可以将PremiumCustomerSpecification与Age18PlusCustomerSpecification结合起来查询高级成人客户的数量，如下所示：

                                        using System;
                                        using System.Threading.Tasks;
                                        using Volo.Abp.DependencyInjection;
                                        using Volo.Abp.Domain.Repositories;
                                        using Volo.Abp.Domain.Services;
                                        using Volo.Abp.Specifications;

                                        namespace MyProject
                                        {
                                            public class CustomerManager : DomainService, ITransientDependency
                                            {
                                                private readonly IRepository<Customer, Guid> _customerRepository;

                                                public CustomerManager(IRepository<Customer, Guid> customerRepository)
                                                {
                                                    _customerRepository = customerRepository;
                                                }

                                                public async Task<int> GetAdultPremiumCustomerCountAsync()
                                                {
                                                    return await _customerRepository.CountAsync(
                                                        new Age18PlusCustomerSpecification()
                                                        .And(new PremiumCustomerSpecification()).ToExpression() // 使用规约的And扩展方法组合规约
                                                    );
                                                }
                                            }
                                        }


                                如果你想使这个组合成为另一个可重用的规范，你可以创建这样一个从AndSpecification派生的组合规范类：

                                        using Volo.Abp.Specifications;

                                        namespace MyProject
                                        {
                                            public class AdultPremiumCustomerSpecification : AndSpecification<Customer>  // 创建一个组合规约类

                                            {
                                                public AdultPremiumCustomerSpecification() 
                                                    : base(new Age18PlusCustomerSpecification(),
                                                        new PremiumCustomerSpecification()) // 调用父类构造函数，将两个规约实例传入
                                                {
                                                }
                                            }
                                        }

                                现在，你可以重写GetAdultPremiumCustomerCountAsync方法，如下所示：

                                        public async Task<int> GetAdultPremiumCustomerCountAsync()
                                        {
                                            return await _customerRepository.CountAsync(
                                                new AdultPremiumCustomerSpecification()
                                            );
                                        }


                                你可以看到这些示例规范的强大功能。

                                如果稍后更改PremiumCustomerSpecification，例如将余额从100.000更改为200.000，所有查询和组合规范都将受到更改的影响。
                                
                                这是减少代码重复的好方法！
                                
                   */

                   /*
                        五、讨论规约

                                虽然规约模式比C# Lambda表达式出现的更早，但它通常与表达式进行比较。

                                一些开发人员可能认为不再需要它，我们可以直接将表达式传递给存储库或域服务，如下所示：

                                         var count = await _customerRepository.CountAsync(c => c.Balance > 100000 && c.Age => 18);
                                         
                                由于ABP的Repository支持表达式，因此这是一个完全有效的用途。

                                你不必在应用程序中定义或使用任何规范，你可以使用表达式。

                                那么，规范的意义是什么？我们为什么以及何时应该考虑使用它们？


                                1、什么时候适合使用规约？

                                          使用规范的一些好处：
                                          
                                               ①、 可重用：想象一下，你在代码库的许多地方都需要高级客户过滤器。

                                                        如果你使用表达式并且没有创建规约，如果你稍后更改“高级客户”定义会发生什么？

                                                        假设你想将最低余额从100,000美元更改为250,000美元，并添加另一个条件以成为超过3年的客户。

                                                        如果你使用了规范，你只需更改一个类。

                                                        如果你在任何地方重复（复制/粘贴）相同的表达式，则需要更改所有这些表达式。

                                               ②、可组合：你可以组合多个规范来创建新规范。这是另一种类型的可重用性。

                                               ③、命名为：PremiumCustomerSpecification更好地解释了意图，而不是复杂的表达式。

                                                         因此，如果你有一个在你的业务中有意义的表达式，请考虑使用规范。

                                               ④、可测试：规范是一个单独（且易于）测试的对象。


                                2、什么时候不适合使用规约？

                                            ①、非业务表达式：不要对非业务相关的表达式和操作使用规范。

                                            ②、报告：如果你只是创建一个报告，不要创建规范，而是直接使用IQueryable&LINQ表达式。

                                                    你甚至可以使用普通的SQL、视图或其他工具进行报告。
                                                    
                                                    DDD不一定关心报告，因此从性能的角度来看，查询底层数据存储的方式可能很重要。
                   */
        </script>
    </body>
</html>